package org.elsfs.cloud.common.mybatis.ext.sql;


import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import org.apache.commons.compress.utils.Lists;
import org.elsfs.cloud.common.core.entity.BaseEntity;
import org.elsfs.cloud.common.mybatis.ext.sql.support.builder.StandardSelectSql;
import org.elsfs.cloud.common.mybatis.ext.sql.support.common.FieldEnumResolverSupport;
import org.elsfs.cloud.common.mybatis.ext.sql.support.common.HierarchyTableAliasManager;
import org.elsfs.cloud.common.mybatis.ext.sql.support.common.SimpleTableAliasManager;
import org.elsfs.cloud.common.mybatis.ext.sql.support.condition.ConditionItem;
import org.elsfs.cloud.common.mybatis.ext.sql.support.select.ExpressionSelectItem;
import org.elsfs.cloud.common.mybatis.ext.sql.support.select.SelectItem;
import org.elsfs.cloud.common.mybatis.ext.sql.support.select.StandardSelectItem;
import org.elsfs.cloud.common.mybatis.ext.sql.support.select.SubQuerySelectItem;
import org.elsfs.cloud.common.util.lang.StringUtils;

import java.util.Collection;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;

/**
 * 抽象查询Join实现
 *
 * @param <C> 子类具体类型
 */
@SuppressWarnings("unchecked")
public abstract class AbstractSelectJoin<C extends AbstractSelectJoin<C>> extends CompositeSqlFragment implements Join<C, AbstractSelectJoin<C>.SelectJoinBuilderImpl, AbstractSelectJoin<C>.SelectJoinBuilderImpl.JoinConditionBuilderImpl>, SqlParameterManagerAware, TableAliasManagerAware {

    /**
     * 具体子类实现引用
     */
    protected final C childThis =  (C) this;
    /**
     * Join构建器列表
     */
    private final List<SelectJoinBuilderImpl> joinBuilders = Lists.newArrayList();

    /**
     * 内连接一张表
     *
     * @param entityClass 实体类
     * @return 连接配置对象
     */
    @Override
    public SelectJoinBuilderImpl join(Class<? extends BaseEntity> entityClass) {
        SelectJoinBuilderImpl joinBuilder = new SelectJoinBuilderImpl(JoinType.INNER_JOIN, TableInfoHelper.getTableInfo(entityClass).getTableName());
        this.joinBuilders.add(joinBuilder);
        return joinBuilder;
    }

    /**
     * 左连接一张表
     *
     * @param entityClass 实体类
     * @return 连接配置对象
     */
    @Override
    public SelectJoinBuilderImpl leftJoin(Class<? extends BaseEntity> entityClass) {
        SelectJoinBuilderImpl joinBuilder = new SelectJoinBuilderImpl(JoinType.LEFT_JOIN, TableInfoHelper.getTableInfo(entityClass).getTableName());
        this.joinBuilders.add(joinBuilder);
        return joinBuilder;
    }

    /**
     * 右连接一张表
     *
     * @param entityClass 实体类
     * @return 连接配置对象
     */
    @Override
    public SelectJoinBuilderImpl rightJoin(Class<? extends BaseEntity> entityClass) {
        SelectJoinBuilderImpl joinBuilder = new SelectJoinBuilderImpl(JoinType.RIGHT_JOIN, TableInfoHelper.getTableInfo(entityClass).getTableName());
        this.joinBuilders.add(joinBuilder);
        return joinBuilder;
    }

    /**
     * 内连接一张表
     *
     * @param entityClass 实体类
     * @param tableAlias  表别名
     * @return 连接配置对象
     */
    @Override
    public SelectJoinBuilderImpl join(Class<? extends BaseEntity> entityClass, String tableAlias) {
        SelectJoinBuilderImpl joinBuilder = new SelectJoinBuilderImpl(JoinType.INNER_JOIN, TableInfoHelper.getTableInfo(entityClass).getTableName(), tableAlias);
        this.joinBuilders.add(joinBuilder);
        return joinBuilder;
    }

    /**
     * 左连接一张表
     *
     * @param entityClass 实体类
     * @param tableAlias  表别名
     * @return 连接配置对象
     */
    @Override
    public SelectJoinBuilderImpl leftJoin(Class<? extends BaseEntity> entityClass, String tableAlias) {
        SelectJoinBuilderImpl joinBuilder = new SelectJoinBuilderImpl(JoinType.LEFT_JOIN, TableInfoHelper.getTableInfo(entityClass).getTableName(), tableAlias);
        this.joinBuilders.add(joinBuilder);
        return joinBuilder;
    }

    /**
     * 右连接一张表
     *
     * @param entityClass 实体类
     * @param tableAlias  表别名
     * @return 连接配置对象
     */
    @Override
    public SelectJoinBuilderImpl rightJoin(Class<? extends BaseEntity> entityClass, String tableAlias) {
        SelectJoinBuilderImpl joinBuilder = new SelectJoinBuilderImpl(JoinType.RIGHT_JOIN, TableInfoHelper.getTableInfo(entityClass).getTableName(), tableAlias);
        this.joinBuilders.add(joinBuilder);
        return joinBuilder;
    }

    /**
     * 内连接一张表
     *
     * @param entityClass   实体类
     * @param tableAlias    表别名
     * @param registerAlias 是否注册别名
     * @return 连接配置对象
     */
    @Override
    public SelectJoinBuilderImpl join(Class<? extends BaseEntity> entityClass, String tableAlias, boolean registerAlias) {
        SelectJoinBuilderImpl joinBuilder = new SelectJoinBuilderImpl(JoinType.INNER_JOIN, TableInfoHelper.getTableInfo(entityClass).getTableName(), tableAlias, registerAlias);
        this.joinBuilders.add(joinBuilder);
        return joinBuilder;
    }

    /**
     * 左连接一张表
     *
     * @param entityClass   实体类
     * @param tableAlias    表别名
     * @param registerAlias 是否注册别名
     * @return 连接配置对象
     */
    @Override
    public SelectJoinBuilderImpl leftJoin(Class<? extends BaseEntity> entityClass, String tableAlias, boolean registerAlias) {
        SelectJoinBuilderImpl joinBuilder = new SelectJoinBuilderImpl(JoinType.LEFT_JOIN, TableInfoHelper.getTableInfo(entityClass).getTableName(), tableAlias, registerAlias);
        this.joinBuilders.add(joinBuilder);
        return joinBuilder;
    }

    /**
     * 右连接一张表
     *
     * @param entityClass   实体类
     * @param tableAlias    表别名
     * @param registerAlias 是否注册别名
     * @return 连接配置对象
     */
    @Override
    public SelectJoinBuilderImpl rightJoin(Class<? extends BaseEntity> entityClass, String tableAlias, boolean registerAlias) {
        SelectJoinBuilderImpl joinBuilder = new SelectJoinBuilderImpl(JoinType.RIGHT_JOIN, TableInfoHelper.getTableInfo(entityClass).getTableName(), tableAlias, registerAlias);
        this.joinBuilders.add(joinBuilder);
        return joinBuilder;
    }

    /**
     * 内连接一张表
     *
     * @param table 数据库表名
     * @return 连接配置对象
     */
    @Override
    public SelectJoinBuilderImpl join(String table) {
        SelectJoinBuilderImpl joinBuilder = new SelectJoinBuilderImpl(JoinType.INNER_JOIN, table);
        this.joinBuilders.add(joinBuilder);
        return joinBuilder;
    }

    /**
     * 左连接一张表
     *
     * @param table 数据库表名
     * @return 连接配置对象
     */
    @Override
    public SelectJoinBuilderImpl leftJoin(String table) {
        SelectJoinBuilderImpl joinBuilder = new SelectJoinBuilderImpl(JoinType.LEFT_JOIN, table);
        this.joinBuilders.add(joinBuilder);
        return joinBuilder;
    }

    /**
     * 右连接一张表
     *
     * @param table 数据库表名
     * @return 连接配置对象
     */
    @Override
    public SelectJoinBuilderImpl rightJoin(String table) {
        SelectJoinBuilderImpl joinBuilder = new SelectJoinBuilderImpl(JoinType.RIGHT_JOIN, table);
        this.joinBuilders.add(joinBuilder);
        return joinBuilder;
    }

    /**
     * 内连接一张表
     *
     * @param table      数据库表名
     * @param tableAlias 表别名
     * @return 连接配置对象
     */
    @Override
    public SelectJoinBuilderImpl join(String table, String tableAlias) {
        SelectJoinBuilderImpl joinBuilder = new SelectJoinBuilderImpl(JoinType.INNER_JOIN, table, tableAlias);
        this.joinBuilders.add(joinBuilder);
        return joinBuilder;
    }

    /**
     * 左连接一张表
     *
     * @param table      数据库表名
     * @param tableAlias 表别名
     * @return 连接配置对象
     */
    @Override
    public SelectJoinBuilderImpl leftJoin(String table, String tableAlias) {
        SelectJoinBuilderImpl joinBuilder = new SelectJoinBuilderImpl(JoinType.LEFT_JOIN, table, tableAlias);
        this.joinBuilders.add(joinBuilder);
        return joinBuilder;
    }

    /**
     * 右连接一张表
     *
     * @param table      数据库表名
     * @param tableAlias 表别名
     * @return 连接配置对象
     */
    @Override
    public SelectJoinBuilderImpl rightJoin(String table, String tableAlias) {
        SelectJoinBuilderImpl joinBuilder = new SelectJoinBuilderImpl(JoinType.RIGHT_JOIN, table, tableAlias);
        this.joinBuilders.add(joinBuilder);
        return joinBuilder;
    }

    /**
     * 内连接一张表
     *
     * @param table         数据库表名
     * @param tableAlias    表别名
     * @param registerAlias 是否注册别名
     * @return 连接配置对象
     */
    @Override
    public SelectJoinBuilderImpl join(String table, String tableAlias, boolean registerAlias) {
        SelectJoinBuilderImpl joinBuilder = new SelectJoinBuilderImpl(JoinType.INNER_JOIN, table, tableAlias, registerAlias);
        this.joinBuilders.add(joinBuilder);
        return joinBuilder;
    }

    /**
     * 左连接一张表
     *
     * @param table         数据库表名
     * @param tableAlias    表别名
     * @param registerAlias 是否注册别名
     * @return 连接配置对象
     */
    @Override
    public SelectJoinBuilderImpl leftJoin(String table, String tableAlias, boolean registerAlias) {
        SelectJoinBuilderImpl joinBuilder = new SelectJoinBuilderImpl(JoinType.LEFT_JOIN, table, tableAlias, registerAlias);
        this.joinBuilders.add(joinBuilder);
        return joinBuilder;
    }

    /**
     * 右连接一张表
     *
     * @param table         数据库表名
     * @param tableAlias    表别名
     * @param registerAlias 是否注册别名
     * @return 连接配置对象
     */
    @Override
    public SelectJoinBuilderImpl rightJoin(String table, String tableAlias, boolean registerAlias) {
        SelectJoinBuilderImpl joinBuilder = new SelectJoinBuilderImpl(JoinType.RIGHT_JOIN, table, tableAlias, registerAlias);
        this.joinBuilders.add(joinBuilder);
        return joinBuilder;
    }

    /**
     * 子查询JOIN
     *
     * @param sqlBuilderConsumer 子查询SQL构建器
     * @param tableAlias 临时表别名
     * @param joinType 连接类型
     * @return 连接配置对象
     */
    private SelectJoinBuilderImpl doSubQueryJoin(Consumer<StandardSelectSql> sqlBuilderConsumer, String tableAlias, JoinType joinType) {
        if (StringUtils.isBlank(tableAlias)) {
            throw new MybatisPlusException("JOIN子查询的表别名不能为空");
        }

        StandardSelectSql standardSelectSql = new StandardSelectSql(
            this.getSqlParameterManager(),
            new HierarchyTableAliasManager(
                new SimpleTableAliasManager(),
                this.getTableAliasManager()
            )
        );
        sqlBuilderConsumer.accept(standardSelectSql);

        SelectJoinBuilderImpl joinBuilder = new SelectJoinBuilderImpl(
            joinType,
                "(" + standardSelectSql.buildSqlFragment() + "(",
            tableAlias
        );
        this.joinBuilders.add(joinBuilder);
        return joinBuilder;
    }

    /**
     * 内连接一个子查询
     *
     * @param sqlBuilderConsumer 子查询构建器
     * @param tableAlias         表别名
     * @return 连接配置对象
     */
    @Override
    public SelectJoinBuilderImpl join(Consumer<StandardSelectSql> sqlBuilderConsumer, String tableAlias) {
        return this.doSubQueryJoin(sqlBuilderConsumer, tableAlias, JoinType.INNER_JOIN);
    }

    /**
     * 左连接一个子查询
     *
     * @param sqlBuilderConsumer 子查询构建器
     * @param tableAlias         表别名
     * @return 连接配置对象
     */
    @Override
    public SelectJoinBuilderImpl leftJoin(Consumer<StandardSelectSql> sqlBuilderConsumer, String tableAlias) {
        return this.doSubQueryJoin(sqlBuilderConsumer, tableAlias, JoinType.LEFT_JOIN);
    }

    /**
     * 右连接一个子查询
     *
     * @param sqlBuilderConsumer 子查询构建器
     * @param tableAlias         表别名
     * @return 连接配置对象
     */
    @Override
    public SelectJoinBuilderImpl rightJoin(Consumer<StandardSelectSql> sqlBuilderConsumer, String tableAlias) {
        return this.doSubQueryJoin(sqlBuilderConsumer, tableAlias, JoinType.RIGHT_JOIN);
    }

    /**
     * 构建SQL之前处理
     */
    @Override
    protected void beforeBuild() {
        for (SelectJoinBuilderImpl joinBuilder : this.joinBuilders) {
            this.addSqlFragment(joinBuilder);
        }
    }

    /**
     * 添加选择项
     *
     * @param selectItem 选择项
     */
    protected abstract void addSelectItem(SelectItem selectItem);

    /**
     * 查询Join构建器实现
     */
    public class SelectJoinBuilderImpl extends CompositeSqlFragment implements SelectJoinBuilder<SelectJoinBuilderImpl, SelectJoinBuilderImpl.JoinConditionBuilderImpl, C>, SqlParameterManagerAware, TableAliasManagerAware {

        /**
         * 连接类型
         */
        private final JoinType joinType;
        /**
         * 连接表名称
         */
        private final String table;
        /**
         * 连接表别名
         */
        private final String tableAlias;
        /**
         * Join条件构建器
         */
        private JoinConditionBuilderImpl joinConditionBuilder;

        public SelectJoinBuilderImpl(JoinType joinType, String table, String tableAlias, boolean registerAlias) {
            this.joinType = joinType;
            this.table = table;
            this.tableAlias = tableAlias;

            if (registerAlias) {
                AbstractSelectJoin.this.getTableAliasManager().registerAlias(table, tableAlias);
            }
        }

        public SelectJoinBuilderImpl(JoinType joinType, String table, String tableAlias) {
            this(
                joinType,
                table,
                tableAlias,
                true
            );
        }

        public SelectJoinBuilderImpl(JoinType joinType, String table) {
            this(
                joinType,
                table,
                AbstractSelectJoin.this.getTableAliasManager().generateAlias(table),
                true
            );
        }

        /**
         * 解析字段枚举名称
         *
         * @param field 字段枚举
         * @return 字段名称
         */
        protected String resolveFieldName(Enum<?> field) {
            return FieldEnumResolverSupport.resolveFieldName(
                this.getTableAliasManager(),
                field
            );
        }

        /**
         * 返回上一级对象
         *
         * @return 上级对象
         */
        @Override
        public C end() {
            return AbstractSelectJoin.this.childThis;
        }

        /**
         * 连接条件设置
         *
         * @return 连接条件构建器
         */
        @Override
        public JoinConditionBuilderImpl on() {
            this.joinConditionBuilder = new JoinConditionBuilderImpl();
            return this.joinConditionBuilder;
        }

        /**
         * 构建SQL之前处理
         */
        @Override
        protected void beforeBuild() {
            this.addSqlFragment(
                new TokenSqlFragment(this.joinType.getJoinKeyword())
            );
            this.addSqlFragment(
                new TokenSqlFragment(
                    this.table + " AS " + this.tableAlias
                )
            );
            this.addSqlFragment(this.joinConditionBuilder);
        }

        /**
         * 获取SQL参数管理器
         *
         * @return SQL参数管理器
         */
        @Override
        public SqlParameterManager getSqlParameterManager() {
            return AbstractSelectJoin.this.getSqlParameterManager();
        }

        /**
         * 选择关联SQL字段
         *
         * @param fields SQL字段
         * @return 具体实现
         */
        @Override
        public SelectJoinBuilderImpl select(String... fields) {
            for (String field : fields) {
                this.addSelectField(field);
            }

            return this;
        }

        /**
         * 选择关联SQL字段表达式
         *
         * @param expressions SQL字段表达式
         * @return 具体实现
         */
        @Override
        public SelectJoinBuilderImpl selectExpression(String... expressions) {
            for (String expression : expressions) {
                this.addSelectExpression(expression);
            }

            return this;
        }

        /**
         * 选择关联SQL字段
         *
         * @param fields SQL字段枚举
         * @return 具体实现
         */
        @Override
        public SelectJoinBuilderImpl select(Enum<?>... fields) {
            for (Enum<?> field : fields) {
                this.addSelectField(
                    this.resolveFieldName(field)
                );
            }

            return this;
        }

        /**
         * 选择SQL字段
         *
         * @param field      SQL字段
         * @param fieldAlias 字段别名
         * @return 具体实现
         */
        @Override
        public SelectJoinBuilderImpl selectAs(String field, String fieldAlias) {
            if (StringUtils.isBlank(fieldAlias)) {
                throw new MybatisPlusException("选择字段"+field+"的别名不能为空");
            }
            this.addSelectField(
                field + " AS "+ fieldAlias
            );

            return this;
        }

        /**
         * 选择SQL字段
         *
         * @param field      SQL字段枚举
         * @param fieldAlias 字段别名
         * @return 具体实现
         */
        @Override
        public SelectJoinBuilderImpl selectAs(Enum<?> field, String fieldAlias) {
            return this.selectAs(
                this.resolveFieldName(field),
                fieldAlias
            );
        }

        /**
         * 选择SQL字段
         *
         * @param field      SQL字段枚举
         * @param fieldAlias 字段别名枚举
         * @return 具体实现
         */
        @Override
        public SelectJoinBuilderImpl selectAs(Enum<?> field, Enum<?> fieldAlias) {
            return this.selectAs(field, fieldAlias.name());
        }

        /**
         * 选择子查询结果
         *
         * @param sqlBuilderConsumer SQL构建器消费器
         * @param fieldAlias         字段别名
         * @return 具体实现
         */
        @Override
        public SelectJoinBuilderImpl selectAs(Consumer<StandardSelectSql> sqlBuilderConsumer, String fieldAlias) {
            if (StringUtils.isBlank(fieldAlias)) {
                throw new MybatisPlusException("选择子查询的字段别名不能为空");
            }

            StandardSelectSql standardSelectSql = new StandardSelectSql(
                this.getSqlParameterManager(),
                new HierarchyTableAliasManager(
                    new SimpleTableAliasManager(),
                    this.getTableAliasManager()
                )
            );
            sqlBuilderConsumer.accept(standardSelectSql);
            this.addSelectSubQuery(
                standardSelectSql,
                fieldAlias
            );
            return this;
        }

        /**
         * 选择子查询结果
         *
         * @param sqlBuilderConsumer SQL构建器消费器
         * @param fieldAlias         字段别名枚举
         * @return 具体实现
         */
        @Override
        public SelectJoinBuilderImpl selectAs(Consumer<StandardSelectSql> sqlBuilderConsumer, Enum<?> fieldAlias) {
            return this.selectAs(sqlBuilderConsumer, fieldAlias.name());
        }

        /**
         * 选择关联SQL字段
         *
         * @param fields SQL字段
         * @return 具体实现
         */
        @Override
        public SelectJoinBuilderImpl select(Collection<String> fields) {
            for (String field : fields) {
                this.addSelectField(field);
            }

            return this;
        }

        /**
         * 添加选择SQL字段
         *
         * @param field SQL字段
         */
        private void addSelectField(String field) {
            StandardSelectItem selectItem = new StandardSelectItem(field);
            if (StringUtils.isBlank(selectItem.getTableAlias())) {
                selectItem = new StandardSelectItem(this.tableAlias, selectItem.getField(), selectItem.getFieldAlias());
            }

            AbstractSelectJoin.this.addSelectItem(selectItem);
        }

        /**
         * 添加选择子查询
         *
         * @param selectSql 子查询SQL
         * @param fieldAlias 字段别名
         */
        private void addSelectSubQuery(StandardSelectSql selectSql, String fieldAlias) {
            AbstractSelectJoin.this.addSelectItem(
                new SubQuerySelectItem(
                    selectSql.buildSqlFragment(),
                    fieldAlias
                )
            );
        }

        /**
         * 添加选择SQL字段表达式
         *
         * @param expression SQL字段表达式
         */
        private void addSelectExpression(String expression) {
            AbstractSelectJoin.this.addSelectItem(
                new ExpressionSelectItem(expression)
            );
        }

        /**
         * 选择关联SQL字段表达式
         *
         * @param expressions SQL字段表达式
         * @return 具体实现
         */
        @Override
        public SelectJoinBuilderImpl selectExpression(Collection<String> expressions) {
            for (String expression : expressions) {
                this.addSelectExpression(expression);
            }

            return this;
        }

        /**
         * 选择关联SQL字段
         *
         * @param fields SQL字段枚举
         * @return 具体实现
         */
        @Override
        public SelectJoinBuilderImpl selectEnum(Collection<Enum<?>> fields) {
            for (Enum<?> field : fields) {
                this.addSelectField(
                    this.resolveFieldName(field)
                );
            }

            return this;
        }

        /**
         * 获取表别名管理器
         *
         * @return 表名别管理器
         */
        @Override
        public TableAliasManager getTableAliasManager() {
            return AbstractSelectJoin.this.getTableAliasManager();
        }

        /**
         * Join条件构建器实现
         */
        public class JoinConditionBuilderImpl extends AbstractCondition<JoinConditionBuilderImpl> implements JoinConditionBuilder<JoinConditionBuilderImpl, SelectJoinBuilderImpl, C> {

            /**
             * Join条件列表
             */
            private final List<ConditionItem> conditionItems = Lists.newArrayList();

            /**
             * 获取SQL参数管理器
             *
             * @return SQL参数管理器
             */
            @Override
            public SqlParameterManager getSqlParameterManager() {
                return SelectJoinBuilderImpl.this.getSqlParameterManager();
            }

            /**
             * 添加一个条件
             *
             * @param conditionItem 条件
             */
            @Override
            protected void addConditionItem(ConditionItem conditionItem) {
                this.conditionItems.add(conditionItem);
            }

            /**
             * 返回上一级对象
             *
             * @return 上级对象
             */
            @Override
            public C end() {
                return SelectJoinBuilderImpl.this.end();
            }

            /**
             * 构建SQL片段
             *
             * @return SQL片段
             */
            @Override
            public String buildSqlFragment() {
                if (this.conditionItems.isEmpty()) {
                    return StringUtils.EMPTY;
                }

                return String.format(
                    "ON %s",
                    ConditionItem.stripHeaderKeyword(
                        this.conditionItems.stream()
                            .map(ConditionItem::buildSqlFragment)
                            .collect(Collectors.joining(StringUtils.SPACE))
                    )
                );
            }

            /**
             * 获取表别名管理器
             *
             * @return 表名别管理器
             */
            @Override
            public TableAliasManager getTableAliasManager() {
                return SelectJoinBuilderImpl.this.getTableAliasManager();
            }
        }
    }
}
